// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/loader/detachable_resource_handler.h"

#include <utility>

#include "base/logging.h"
#include "base/time/time.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_status.h"

namespace {
// This matches the maximum allocation size of AsyncResourceHandler.
const int kReadBufSize = 32 * 1024;
}

namespace content {

DetachableResourceHandler::DetachableResourceHandler(
    net::URLRequest* request,
    base::TimeDelta cancel_delay,
    std::unique_ptr<ResourceHandler> next_handler)
    : ResourceHandler(request)
    , next_handler_(std::move(next_handler))
    , cancel_delay_(cancel_delay)
    , is_deferred_(false)
    , is_finished_(false)
{
    GetRequestInfo()->set_detachable_handler(this);
}

DetachableResourceHandler::~DetachableResourceHandler()
{
    // Cleanup back-pointer stored on the request info.
    GetRequestInfo()->set_detachable_handler(NULL);
}

void DetachableResourceHandler::Detach()
{
    if (is_detached())
        return;

    if (!is_finished_) {
        // Simulate a cancel on the next handler before destroying it.
        net::URLRequestStatus status(net::URLRequestStatus::CANCELED,
            net::ERR_ABORTED);
        bool defer_ignored = false;
        next_handler_->OnResponseCompleted(status, &defer_ignored);
        DCHECK(!defer_ignored);
        // If |next_handler_| were to defer its shutdown in OnResponseCompleted,
        // this would destroy it anyway. Fortunately, AsyncResourceHandler never
        // does this anyway, so DCHECK it. MimeTypeResourceHandler and RVH shutdown
        // already ignore deferred ResourceHandler shutdown, but
        // DetachableResourceHandler and the detach-on-renderer-cancel logic
        // introduces a case where this occurs when the renderer cancels a resource.
    }
    // A OnWillRead / OnReadCompleted pair may still be in progress, but
    // OnWillRead passes back a scoped_refptr, so downstream handler's buffer will
    // survive long enough to complete that read. From there, future reads will
    // drain into |read_buffer_|. (If |next_handler_| is an AsyncResourceHandler,
    // the net::IOBuffer takes a reference to the ResourceBuffer which owns the
    // shared memory.)
    next_handler_.reset();

    // Time the request out if it takes too long.
    detached_timer_.reset(new base::OneShotTimer());
    detached_timer_->Start(
        FROM_HERE, cancel_delay_, this, &DetachableResourceHandler::Cancel);

    // Resume if necessary. The request may have been deferred, say, waiting on a
    // full buffer in AsyncResourceHandler. Now that it has been detached, resume
    // and drain it.
    if (is_deferred_) {
        // The nested ResourceHandler may have logged that it's blocking the
        // request.  Log it as no longer doing so, to avoid a DCHECK on resume.
        request()->LogUnblocked();
        Resume();
    }
}

void DetachableResourceHandler::SetController(ResourceController* controller)
{
    ResourceHandler::SetController(controller);

    // Intercept the ResourceController for downstream handlers to keep track of
    // whether the request is deferred.
    if (next_handler_)
        next_handler_->SetController(this);
}

bool DetachableResourceHandler::OnRequestRedirected(
    const net::RedirectInfo& redirect_info,
    ResourceResponse* response,
    bool* defer)
{
    DCHECK(!is_deferred_);

    if (!next_handler_)
        return true;

    bool ret = next_handler_->OnRequestRedirected(
        redirect_info, response, &is_deferred_);
    *defer = is_deferred_;
    return ret;
}

bool DetachableResourceHandler::OnResponseStarted(ResourceResponse* response,
    bool* defer)
{
    DCHECK(!is_deferred_);

    if (!next_handler_)
        return true;

    bool ret = next_handler_->OnResponseStarted(response, &is_deferred_);
    *defer = is_deferred_;
    return ret;
}

bool DetachableResourceHandler::OnWillStart(const GURL& url, bool* defer)
{
    DCHECK(!is_deferred_);

    if (!next_handler_)
        return true;

    bool ret = next_handler_->OnWillStart(url, &is_deferred_);
    *defer = is_deferred_;
    return ret;
}

bool DetachableResourceHandler::OnWillRead(scoped_refptr<net::IOBuffer>* buf,
    int* buf_size,
    int min_size)
{
    if (!next_handler_) {
        DCHECK_EQ(-1, min_size);
        if (!read_buffer_.get())
            read_buffer_ = new net::IOBuffer(kReadBufSize);
        *buf = read_buffer_;
        *buf_size = kReadBufSize;
        return true;
    }

    return next_handler_->OnWillRead(buf, buf_size, min_size);
}

bool DetachableResourceHandler::OnReadCompleted(int bytes_read, bool* defer)
{
    DCHECK(!is_deferred_);

    if (!next_handler_)
        return true;

    bool ret = next_handler_->OnReadCompleted(bytes_read, &is_deferred_);
    *defer = is_deferred_;
    return ret;
}

void DetachableResourceHandler::OnResponseCompleted(
    const net::URLRequestStatus& status,
    bool* defer)
{
    // No DCHECK(!is_deferred_) as the request may have been cancelled while
    // deferred.

    if (!next_handler_)
        return;

    is_finished_ = true;

    next_handler_->OnResponseCompleted(status, &is_deferred_);
    *defer = is_deferred_;
}

void DetachableResourceHandler::OnDataDownloaded(int bytes_downloaded)
{
    if (!next_handler_)
        return;

    next_handler_->OnDataDownloaded(bytes_downloaded);
}

void DetachableResourceHandler::Resume()
{
    DCHECK(is_deferred_);
    is_deferred_ = false;
    controller()->Resume();
}

void DetachableResourceHandler::Cancel()
{
    controller()->Cancel();
}

void DetachableResourceHandler::CancelAndIgnore()
{
    controller()->CancelAndIgnore();
}

void DetachableResourceHandler::CancelWithError(int error_code)
{
    controller()->CancelWithError(error_code);
}

} // namespace content
